/**
 * <p>Original Author: Daniel Freeman    http://www.danielfreeman.asia</p>
 *
 * <p>Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:</p>
 *
 * <p>The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.</p>
 *
 * <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS' OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.</p>
 *
 * <p>Licensed under The MIT License</p>
 * <p>Redistributions of files must retain the above copyright notice.</p>
 * <p>You are permitted to modify and enhance this code,</p>
 * <p>As a courtesy, please credit the original author, Daniel Freeman   http://www.danielfreeman.asia</p>
 */

// AGAL Animation

package {
	[SWF(frameRate="60")]

	import com.adobe.utils.*;

	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DBlendFactor;
	import flash.display3D.Context3DCompareMode;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.textures.Texture;
	import flash.events.Event;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;


	public class Flash3dExperimentE extends Sprite {

		protected static const STEPS:int=14;
		protected static const STEPSM:int=STEPS - 1;

		protected static const N:int=5;

		protected var _indices:Vector.<uint>=new Vector.<uint>;

		protected var _vertices:Vector.<Number>=new Vector.<Number>;

		// The usual Stage3D stuff
		protected var _context3D:Context3D;
		protected var _shaderProgram:Program3D;
		protected var _vertexBuffer:VertexBuffer3D;
		protected var _indexBuffer:IndexBuffer3D;

		// Transformation Matrices
		protected var _modelMatrix:Matrix3D=new Matrix3D();
		protected var _rotationMatrix:Matrix3D;
		protected var _projectionMatrix:PerspectiveMatrix3D=new PerspectiveMatrix3D();

		protected var _t:Number=0;

		public function Flash3dExperimentE(screen:Sprite=null) {
			if (screen) {
				screen.addChild(this);
			}

			stage.scaleMode=StageScaleMode.NO_SCALE;
			stage.align=StageAlign.TOP_LEFT;

			generateSphereVerticesAndCodes();
			generateSphereIndices();

			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
		}


		protected function onContext3DCreate(event:Event):void {
			_context3D=Stage3D(event.currentTarget).context3D;

			if (!_context3D) {
				return;
			}

			_context3D.enableErrorChecking=true;
			initialiseData();
		}


		protected function generateSphereVerticesAndCodes():void {
			_vertices.push(0.0, -1.0, 0.0, 0.0, 0.0);
			for (var i:int=0; i < STEPS; i++) {
				for (var j:int=1; j < STEPS; j++) {
					var phi:Number=2 * Math.PI * (i / STEPS);
					var theta:Number=Math.PI * (j / STEPS - 0.5);
					_vertices.push(Math.cos(theta) * Math.cos(phi), Math.sin(theta), Math.cos(theta) * Math.sin(phi), (i % 2 == 0) ? 0.0 : 1.0, (j % 2 == 0) ? 0.0 : 1.0);
				}
			}
			_vertices.push(0.0, 1.0, 0.0, 0.0, 0.0);
		}


		protected function generateSphereIndices():void {
			var lastLine:int=STEPSM * STEPSM;
			for (var i:int=0; i < STEPS; i++) {
				_indices.push(0, i * STEPSM + 1, lastLine + 1);
				for (var j:int=1; j < STEPSM; j++) {
					_indices.push(lastLine + j, i * STEPSM + j + 1, lastLine + j + 1);
					_indices.push(lastLine + j, i * STEPSM + j, i * STEPSM + j + 1);
				}
				_indices.push(lastLine + STEPSM, (i + 1) * STEPSM, _vertices.length / N - 1);
				lastLine=i * STEPSM;
			}
		}


		protected function initialiseData():void {

			// Back buffer
			_context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 1, true);
			_context3D.setCulling(Context3DTriangleFace.FRONT);

			// Upload indexes
			_indexBuffer=_context3D.createIndexBuffer(_indices.length);
			_indexBuffer.uploadFromVector(_indices, 0, _indices.length);

			// Upload vertex data
			_vertexBuffer=_context3D.createVertexBuffer(_vertices.length / N, N);
			_vertexBuffer.uploadFromVector(_vertices, 0, _vertices.length / N);

			var vertexShaderAssembler:AGALMiniAssembler=new AGALMiniAssembler();
			vertexShaderAssembler.assemble(Context3DProgramType.VERTEX, "mov v1, va1 \n" + // interpolate uv		
				"m44 vt0, va0, vc0 \n" + // translate vertex
				"m44 vt1, va0, vc4 \n" + // translate normal
				"mov v0, vt1 \n" + // interpolate normal
				"mul vt1.xyz, vc8.xxx, vt1.xyz \n" + // multiply t by normal
				"mul vt2.x, va1.x, va1.y \n" + // multiply u by v... ( u==1 && v==1) ? 1 : 0
				"mul vt1.xyz, vt2.xxx, vt1.xyz \n" + // if u and v are 1, vt1 is scaled normal, otherwise 
				"add op, vt0, vt1" // add 
				);


			var fragmentShaderAssembler:AGALMiniAssembler=new AGALMiniAssembler();
			fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT,

				//This first part is a simple shader
				"nrm ft0.xyz, v0 \n" + // normalise normal
				"mov ft0.w, fc0.w \n" + // set the w component to 1

				"dp3 ft0.x, ft0, fc0 \n" + // dot product between normal and half-angle
				"sat ft0.x, ft0.x \n" + // clip value between 0 and 1

				"mul ft2.xyz, fc2.xyz, ft0.xxx \n" + // diffuse colour x (dot product)
				"add ft2.xyz, fc1.xyz, ft2.xyz \n" + // add ambient colour

				//This second part draws the wireframe
				"sub ft0.x, fc0.w, v1.x \n" + "sub ft0.y, fc0.w, v1.y \n" +

				"min ft0.z, ft0.x, ft0.y \n" + "min ft0.z, ft0.z, v1.x \n" + "min ft0.z, ft0.z, v1.y \n" +

				"div ft0.z, fc3.w, ft0.z \n" + "sat ft0.z, ft0.z \n" +

				"mul ft1.xyz, fc3.xyz, ft0.zzz \n" + "add oc, ft2.xyz, ft1.xyz");

			// Combine assembled shaders then upload to the GPU
			_shaderProgram=_context3D.createProgram();
			_shaderProgram.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);

			// Perspective transformation
			_projectionMatrix.perspectiveFieldOfViewLH(45.0 * Math.PI / 180, stage.stageWidth / stage.stageHeight, 0.1, 1000.0);

			// Start animating
			addEventListener(Event.ENTER_FRAME, enterFrame);
		}


		protected function normalise4(p:Number, q:Number, r:Number, quotient:Number=0):Vector.<Number> {
			if (quotient == 0) {
				quotient=Math.sqrt(p * p + q * q + r * r);
			}
			return Vector.<Number>([p / quotient, q / quotient, r / quotient, 1.0]);
		}


		protected function enterFrame(event:Event):void {
			_context3D.clear();

			_context3D.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); //va0
			_context3D.setVertexBufferAt(1, _vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2); //va1

			_context3D.setProgram(_shaderProgram);

			// Update Rotation
			_modelMatrix.identity();
			_modelMatrix.appendRotation(_t * 0.2, Vector3D.Y_AXIS);
			_modelMatrix.appendRotation(_t * 0.2, Vector3D.X_AXIS);
			_modelMatrix.appendRotation(_t * 0.5, Vector3D.Z_AXIS);
			_rotationMatrix=_modelMatrix.clone();

			_modelMatrix.appendScale(0.5, 0.5, 0.5);
			_modelMatrix.appendTranslation(0, 0, 2);
			_modelMatrix.append(_projectionMatrix);

			_t+=4.0;

			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _modelMatrix, true); // vc0 - vc3
			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 4, _rotationMatrix, true); // vc4 - vc7
			var tCos:Number=0.5 - Math.cos((_t % (3141 * 2)) / 10) / 2;
			_context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 8, Vector.<Number>([tCos, 0.0, 0.0, 1.0])); // vc8

			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, normalise4(2, 1, -3)); // fc0 Half Angle Vector
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0.1 + tCos * 0.1, 0.1 + tCos * 0.2, 0.2, 1.0])); // fc1 Ambient Colour
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, Vector.<Number>([0.3 + tCos * 0.5, 0.3, 0.3, 1.0])); // fc2 Diffuse Colour

			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 3, Vector.<Number>([0.2, 0.2, 0.2, 0.01])); // fc3 Line Colour + line width

			_context3D.drawTriangles(_indexBuffer);

			_context3D.present();
		}

	}
}
